﻿// ArduinoStepperRotator
// 
// Arduino stepper motor rotation 
// 
// When a rotation step is triggered, ArduinoStepperRotator simply sends out
// a direction character "+" or "-" and an integer number (encoded as byte) via the serial port. 
// The corresponding Arduino program listens to the serial port. When Arduino 
// receives a character, it moves the stepper motor for the transmitted
// number of steps in the transmitted direction.
// 
// FLMScan - Featured by FabLab Munich

using System;
using System.Threading;
using System.ComponentModel;
using log4net;
using FLMScan.Exception;
using FLMScan.Serial;

namespace FLMScan.Rotator
{
    class ArduinoStepperRotator : IDevice, IStepRotator
    {
        private ILog logger;
        private bool isDisposed = false;
        private bool isConnected = false;        

        private int serialPortIndex = 1;
        private int maxStepCount;
        private SerialPortWrapper serialPortWrapper;
        private int microStepsPerStep = 1;
        private int totalStepCount;
        private int currentStep = 0;
        private char[] buffer;

        private BackgroundWorker backgroundWorker;
        
        private const string LogArduinoDeviceInitializationFailed = "Arduino initialization failed. Arduino is probably not connected or invalid COM port index ";
        private const string LogArduinoDeviceInitializationSucceeded = "Arduino initialization succeeded. Connected COM port ";
        private const string LogArduinoStepperRotatorStepFailed = "Stepping Arduino rotator failed. Arduino was probably disconnected.";                                

        private event EventHandler StepDone;
                
        event EventHandler IStepRotator.StepDone
        {
            add { StepDone += value; }
            remove { StepDone -= value; }
        }
        
        public ArduinoStepperRotator(int serialPortIndex, int maxStepCount)
        {            
            try
            {
                logger = LogManager.GetLogger(typeof(ArduinoStepperRotator));

                this.serialPortIndex = serialPortIndex;
                this.maxStepCount = maxStepCount;
                this.totalStepCount = this.maxStepCount;
                buffer = new char[2];
                backgroundWorker = new BackgroundWorker();
                backgroundWorker.DoWork += new DoWorkEventHandler(DoStep);                
                backgroundWorker.RunWorkerCompleted += WorkCompleted;                
                Initialize();
            }
            catch (SerialPortException)
            {
                // tolerate serial port exception on startup
                logger.Warn(LogArduinoDeviceInitializationFailed + serialPortIndex);
            }                        
        }

        public bool IsConnected
        {
            get { return isConnected; }
        }

        public void Initialize()
        {
            try
            {
                RealSerialPort realSerialPort = RealSerialPort.Instance(serialPortIndex);
                serialPortWrapper = new SerialPortWrapper(realSerialPort);
                
                isConnected = true;
                logger.Info(LogArduinoDeviceInitializationSucceeded + serialPortIndex);
            }
            catch (SerialPortException)
            {
                // SerialPortException is handled by our caller FLMScanController
                throw;
            }
        }

        public void SetTotalStepCount(int stepCount)
        {
            if ((stepCount > 0) && (stepCount <= maxStepCount))
            {
                totalStepCount = stepCount;
                microStepsPerStep = maxStepCount / totalStepCount;
            }
        }

        public bool Step()
        {
            bool result = true;

            if (currentStep < totalStepCount)
            {
                ++currentStep;
                backgroundWorker.RunWorkerAsync();                    
            }
            else
            {
                result = false;
            }

            return result;
        }

        public void Reset()
        {
            currentStep = 0;
        }

        // BackgroundWorker work
        private void DoStep(object sender, DoWorkEventArgs e)        
        {                        
            buffer[0] = '+';
            buffer[1] = (char)microStepsPerStep;

            try
            {
                serialPortWrapper.Write(buffer, 0, 2);
            }
            catch (SerialPortException ex)
            {
                // we can't do a meaningful exception handling in this worker thread
                // therefore we simply log the exception
                logger.Error(LogArduinoStepperRotatorStepFailed + ex);                
            }

            Thread.Sleep(1000);                            
        }

        // BackgroundWorker callback on completion
        private void WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (StepDone != null)
            {
                StepDone(this, EventArgs.Empty);
            }   
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool isDisposing)
        {
            if (isDisposed)
                return;

            if (isDisposing)
            {
                isDisposed = true;                
            }
        }   
    }
}
